#include <stdint.h>
#include <string.h>

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

#include "infrastructure.h"

#include "Controller.h"
#include "DisplayManager.h"
#include "KeypadManager.h"
#include "ConsoleManager.h"
#include "HAL.h"
#include "TemperatureMonitor.h"

static xQueueHandle Controller_actualTemperature_queue;
static int32_t Controller_desiredTemperature_data;
static xSemaphoreHandle Controller_desiredTemperature_mutex;
static xSemaphoreHandle DisplayManager_semaphore;
static int32_t DisplayManager_desiredTemperature_data;
static xSemaphoreHandle DisplayManager_desiredTemperature_mutex;
static int32_t DisplayManager_actualTemperature_data;
static xSemaphoreHandle DisplayManager_actualTemperature_mutex;
static int DisplayManager_power_data;
static xSemaphoreHandle DisplayManager_power_mutex;
static xQueueHandle KeypadManager_keypad_queue;
static xSemaphoreHandle ConsoleManager_semaphore;
static int32_t ConsoleManager_desiredTemperature_data;
static xSemaphoreHandle ConsoleManager_desiredTemperature_mutex;
static int32_t ConsoleManager_actualTemperature_data;
static xSemaphoreHandle ConsoleManager_actualTemperature_mutex;
static int ConsoleManager_power_data;
static xSemaphoreHandle ConsoleManager_power_mutex;
static xQueueHandle HAL_power_queue;
static xQueueHandle TemperatureMonitor_temperatureDrop_queue;

void initializeInfrastructure(void) {
	Controller_actualTemperature_queue = xQueueCreate(3, sizeof(int32_t));
	Controller_desiredTemperature_mutex = xSemaphoreCreateMutex();
	DisplayManager_semaphore = xSemaphoreCreateCounting(1, 0);
	DisplayManager_desiredTemperature_mutex = xSemaphoreCreateMutex();
	DisplayManager_actualTemperature_mutex = xSemaphoreCreateMutex();
	DisplayManager_power_mutex = xSemaphoreCreateMutex();
	KeypadManager_keypad_queue = xQueueCreate(3, sizeof(KeypadEvent));
	ConsoleManager_semaphore = xSemaphoreCreateCounting(1, 0);
	ConsoleManager_desiredTemperature_mutex = xSemaphoreCreateMutex();
	ConsoleManager_actualTemperature_mutex = xSemaphoreCreateMutex();
	ConsoleManager_power_mutex = xSemaphoreCreateMutex();
	HAL_power_queue = xQueueCreate(1, sizeof(int32_t));
	TemperatureMonitor_temperatureDrop_queue = xQueueCreate(1, sizeof(int));
}

void Controller_Task(void *parameters) {
	Controller_Runnable_initialize();
	for (;;) {
		{
			int32_t actualTemperature; /* Dummy */
			xQueuePeek(Controller_actualTemperature_queue, (void *) &actualTemperature, portMAX_DELAY);
		}
		Controller_Runnable();
	}
}

void DisplayManager_Task(void *parameters) {
	DisplayManager_Runnable_initialize();
	for (;;) {
		xSemaphoreTake(DisplayManager_semaphore, portMAX_DELAY);
		DisplayManager_Runnable();
	}
}

void KeypadManager_Task(void *parameters) {
	KeypadManager_Runnable_initialize();
	for (;;) {
		KeypadManager_Runnable();
	}
}

void ConsoleManager_Task(void *parameters) {
	ConsoleManager_Runnable_initialize();
	for (;;) {
		xSemaphoreTake(ConsoleManager_semaphore, portMAX_DELAY);
		ConsoleManager_Runnable();
	}
}

void HAL_Task(void *parameters) {
	HAL_Runnable_initialize();
	for (;;) {
		HAL_Runnable();
	}
}

void TemperatureMonitor_Task(void *parameters) {
	TemperatureMonitor_Runnable_initialize();
	for (;;) {
		{
			int temperatureDrop; /* Dummy */
			xQueuePeek(TemperatureMonitor_temperatureDrop_queue, (void *) &temperatureDrop, portMAX_DELAY);
		}
		TemperatureMonitor_Runnable();
	}
}

int Controller_actualTemperature_receive(int32_t *actualTemperature) {
	return xQueueReceive(Controller_actualTemperature_queue, (void *) actualTemperature, (portTickType) 0) == pdTRUE;
}

int Controller_desiredTemperature_receive(int32_t *desiredTemperature) {
	if (xSemaphoreTake(Controller_desiredTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		*desiredTemperature = Controller_desiredTemperature_data;
		return xSemaphoreGive(Controller_desiredTemperature_mutex) == pdTRUE;
	}
	return 0;
}

int Controller_power_send(const int32_t *power) {
	if (xSemaphoreTake(DisplayManager_power_mutex, portMAX_DELAY) == pdTRUE) {
		DisplayManager_power_data = *power;
		xSemaphoreGive(DisplayManager_power_mutex);
		xSemaphoreGive(DisplayManager_semaphore);
	}

	if (xSemaphoreTake(ConsoleManager_power_mutex, portMAX_DELAY) == pdTRUE) {
		ConsoleManager_power_data = *power;
		xSemaphoreGive(ConsoleManager_power_mutex);
		xSemaphoreGive(ConsoleManager_semaphore);
	}

	xQueueSend(HAL_power_queue, (const void *) power, (portTickType) 0);

	return 1;
}

int Controller_temperatureDrop_send(const int *temperatureDrop) {
	{
		static int hasPreviousTemperatureDrop = 0;
		static int previousTemperatureDrop;

		if (!hasPreviousTemperatureDrop || *temperatureDrop != previousTemperatureDrop) {
			xQueueSend(TemperatureMonitor_temperatureDrop_queue, (const void *) temperatureDrop, (portTickType) 0);

			previousTemperatureDrop = *temperatureDrop;
			hasPreviousTemperatureDrop = 1;
		}
	}

	return 1;
}

int DisplayManager_desiredTemperature_receive(int32_t *desiredTemperature) {
	if (xSemaphoreTake(DisplayManager_desiredTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		*desiredTemperature = DisplayManager_desiredTemperature_data;
		return xSemaphoreGive(DisplayManager_desiredTemperature_mutex) == pdTRUE;
	}
	return 0;
}

int DisplayManager_actualTemperature_receive(int32_t *actualTemperature) {
	if (xSemaphoreTake(DisplayManager_actualTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		*actualTemperature = DisplayManager_actualTemperature_data;
		return xSemaphoreGive(DisplayManager_actualTemperature_mutex) == pdTRUE;
	}
	return 0;
}

int DisplayManager_power_receive(int *power) {
	if (xSemaphoreTake(DisplayManager_power_mutex, portMAX_DELAY) == pdTRUE) {
		*power = DisplayManager_power_data;
		return xSemaphoreGive(DisplayManager_power_mutex) == pdTRUE;
	}
	return 0;
}

int DisplayManager_display_getWidth() {
	extern int HAL_display_getWidth();
	return HAL_display_getWidth();
}

int DisplayManager_display_getHeight() {
	extern int HAL_display_getHeight();
	return HAL_display_getHeight();
}

void DisplayManager_display_getStringSize(const char *s, int *width, int *height) {
	extern void HAL_display_getStringSize(const char *s, int *width, int *height);
	HAL_display_getStringSize(s, width, height);
}

void DisplayManager_display_drawString(const char *s, int x, int y) {
	extern void HAL_display_drawString(const char *s, int x, int y);
	HAL_display_drawString(s, x, y);
}

int KeypadManager_keypad_receive(KeypadEvent *keypad) {
	return xQueueReceive(KeypadManager_keypad_queue, (void *) keypad, portMAX_DELAY) == pdTRUE;
}

int KeypadManager_desiredTemperature_send(const int32_t *desiredTemperature) {
	if (xSemaphoreTake(Controller_desiredTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		Controller_desiredTemperature_data = *desiredTemperature;
		xSemaphoreGive(Controller_desiredTemperature_mutex);
	}

	if (xSemaphoreTake(DisplayManager_desiredTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		DisplayManager_desiredTemperature_data = *desiredTemperature;
		xSemaphoreGive(DisplayManager_desiredTemperature_mutex);
		xSemaphoreGive(DisplayManager_semaphore);
	}

	if (xSemaphoreTake(ConsoleManager_desiredTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		ConsoleManager_desiredTemperature_data = *desiredTemperature;
		xSemaphoreGive(ConsoleManager_desiredTemperature_mutex);
		xSemaphoreGive(ConsoleManager_semaphore);
	}

	return 1;
}

int ConsoleManager_desiredTemperature_receive(int32_t *desiredTemperature) {
	if (xSemaphoreTake(ConsoleManager_desiredTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		*desiredTemperature = ConsoleManager_desiredTemperature_data;
		return xSemaphoreGive(ConsoleManager_desiredTemperature_mutex) == pdTRUE;
	}
	return 0;
}

int ConsoleManager_actualTemperature_receive(int32_t *actualTemperature) {
	if (xSemaphoreTake(ConsoleManager_actualTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		*actualTemperature = ConsoleManager_actualTemperature_data;
		return xSemaphoreGive(ConsoleManager_actualTemperature_mutex) == pdTRUE;
	}
	return 0;
}

int ConsoleManager_power_receive(int *power) {
	if (xSemaphoreTake(ConsoleManager_power_mutex, portMAX_DELAY) == pdTRUE) {
		*power = ConsoleManager_power_data;
		return xSemaphoreGive(ConsoleManager_power_mutex) == pdTRUE;
	}
	return 0;
}

void ConsoleManager_console_writeString(const char *s) {
	extern void HAL_uart_writeString(const char *s);
	HAL_uart_writeString(s);
}

int HAL_keypad_send(const KeypadEvent *keypad) {
	portBASE_TYPE higherPriorityTaskWoken = pdFALSE;

	xQueueSendFromISR(KeypadManager_keypad_queue, (const void *) keypad, &higherPriorityTaskWoken);

	if (higherPriorityTaskWoken) {
		taskYIELD();
	}

	return 1;
}

int HAL_actualTemperature_send(const int32_t *actualTemperature) {
	portBASE_TYPE higherPriorityTaskWoken = pdFALSE;

	xQueueSendFromISR(Controller_actualTemperature_queue, (const void *) actualTemperature, &higherPriorityTaskWoken);

	if (xSemaphoreTake(DisplayManager_actualTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		DisplayManager_actualTemperature_data = *actualTemperature;
		xSemaphoreGiveFromISR(DisplayManager_actualTemperature_mutex, &higherPriorityTaskWoken);
	}

	if (xSemaphoreTake(ConsoleManager_actualTemperature_mutex, portMAX_DELAY) == pdTRUE) {
		ConsoleManager_actualTemperature_data = *actualTemperature;
		xSemaphoreGiveFromISR(ConsoleManager_actualTemperature_mutex, &higherPriorityTaskWoken);
	}

	if (higherPriorityTaskWoken) {
		taskYIELD();
	}

	return 1;
}

int HAL_power_receive(int32_t *power) {
	return xQueueReceive(HAL_power_queue, (void *) power, portMAX_DELAY) == pdTRUE;
}

int TemperatureMonitor_temperatureDrop_receive(int *temperatureDrop) {
	return xQueueReceive(TemperatureMonitor_temperatureDrop_queue, (void *) temperatureDrop, (portTickType) 0) == pdTRUE;
}

int TemperatureMonitor_display_getWidth() {
	extern int HAL_display_getWidth();
	return HAL_display_getWidth();
}

int TemperatureMonitor_display_getHeight() {
	extern int HAL_display_getHeight();
	return HAL_display_getHeight();
}

void TemperatureMonitor_display_getStringSize(const char *s, int *width, int *height) {
	extern void HAL_display_getStringSize(const char *s, int *width, int *height);
	HAL_display_getStringSize(s, width, height);
}

void TemperatureMonitor_display_drawString(const char *s, int x, int y) {
	extern void HAL_display_drawString(const char *s, int x, int y);
	HAL_display_drawString(s, x, y);
}

